home *** CD-ROM | disk | FTP | other *** search
Text File | 1994-07-15 | 33.2 KB | 1,107 lines | [TEXT/MPS ] |
- #include "ArrangeCallbacks.h"
- #include "PluginLibrary.h"
-
- #include <Dialogs.h>
- #include <Files.h>
- #include <Memory.h>
- #include <String.h>
- #include <TextUtils.h>
- #include <OSUtils.h>
-
-
- /* This file contains the code for an Import/Export filter plugin that is
- * specific to a tab-delimited export format generated by MeetingMaker.
- * This plugin understands this custom format, will create note and field
- * definitions in the target document to store the imported info into, will
- * perform simple data transformations on the incoming data (and their inverses
- * on the outgoing data) to make the data fit the Arrange model more closely, and
- * will filter for redundant information in the target document, and suppress
- * duplicated notes. This is documented in more detail below.
- *
- * This file also contains a very simple read stream class that will cache disk
- * access in 4K chunks.
- *
- * Based on GenericPlugin.cp as of 7/15/94.
- */
-
- #define ModuleRsrcID 0xFFFF8400
-
- #define baseCmdCode 0x12000200 // replace this with a value obtained from
- // Common Knowledge.
-
- #define aboutCmdCode (baseCmdCode + 0)
-
-
- // STR# resource IDs and indicies
- #define rFieldNameStrings ModuleRsrcID
- #define rNoteTypeName 10
-
- #define rMMImportExportNames ModuleRsrcID + 1
- #define rImportName 1
- #define rExportName 2
-
-
- class SimpleStream;
-
- /* This is the list of fields in the standard format that we are importing.
- * The values of the enum correspond to indicies in an STR# resource
- * that contain the actual names of the Arrange fields that correspond
- * to each MeetingMaker field. The ID of this STR# is rFieldNameStrings.
- */
- enum MeetingMakerField
- {
- mmTitle = 0,
- mmLocation,
- mmDate,
- mmStartTime,
- mmDuration,
- mmPrivate,
- mmFlexible,
- mmLabel,
- mmNotes
- };
-
-
- class Plugin
- {
- public:
- Plugin(const ArrangeCallbackTbl* theCalls);
- ~Plugin();
-
- arHookResult ClickNotify( arClickLocation loc, Point where, Short modifiers,
- Short clickCount, arNoteID note, arFieldID field,
- arPathID path );
- arHookResult KeyNotify ( Short theChar, Short key, Short modifiers );
- arHookResult MenuNotify ( Integer commandCode, Integer commandParam,
- Short modifiers );
- arHookResult FieldNotify( arNoteID note, arFieldID field, arFieldAction action,
- const char* choiceText );
- void TopicNotify( arTopicID newTopic, arWindowID newWindow,
- arTopicAction action );
- void TickNotify ( );
- arHookResult FileNotify ( arFileAction action );
- arHookResult QuitNotify ( );
- void ATMNotify ( );
-
- Integer ImportNotify( Short fRefnum, Integer formatID,
- Boolean sniff, arTopicID destTopic );
-
- Integer ExportNotify( Short fRefnum, arTopicID srcTopic );
-
- private:
- const ArrangeCallbackTbl* calls; // callback table
-
- arNoteID GenerateMMNote(SimpleStream *stream, arTypeID type, char *readBuffer);
- arTypeID AllocateFieldsAndTypes();
- arFieldID GetArrangeFieldForMMField(MeetingMakerField field);
-
- Boolean MMNoteExists(arNoteID newNote);
- void WriteMMNote(arNoteID note,Short fRefNum);
- }; // Plugin
-
-
- /*************************************************************************/
- /**************************** Main entry point ***************************/
- /*************************************************************************/
-
- /* Root entry point for the module - must be the first function in the
- * file, so that the linker will place it first in the code segment.
- * This is the routine which Arrange calls at application startup time
- * (and again at quit time).
- *
- * Most plug-ins will not need to modify this routine or the Hook functions
- * immediately following. All customization can be done in the "Plugin"
- * section (below).
- */
- extern "C" Integer OurModuleRoot( ModuleParamBlock *pb, ModuleRootAction action,
- Integer /*p1*/ )
- {
- /* Extract the callback table from our parameter block, and coerce it
- * to the extended Arrange table.
- */
- ArrangeCallbackTbl* calls = (ArrangeCallbackTbl*)(pb->calls);
-
- switch (action)
- {
- case mrLoad:
- {
- // Allocate memory and create a new Plugin object.
- void* storage = calls->mem->AllocMem( sizeof(Plugin),
- amFreeStore | amErrIfNoMem );
- pb->moduleRefcon = uInteger(new(storage) Plugin(calls));
- return 1;
- }
-
- case mrUnload:
- {
- /* Dispose of the Plugin object and release the memory
- * it occupies.
- */
- delete (Plugin*)(pb->moduleRefcon);
- calls->mem->DeallocMem((void*)(pb->moduleRefcon), amFreeStore);
- return 0;
- }
-
- case mrFindEntry:
- default:
- {
- /* This module contains no extended entry points, so we always
- * return 0 for the mrFindEntry message. Most plug-ins will not
- * use extended entry points.
- */
- return 0;
- }
-
- } // switch (action)
-
- } // OurModuleRoot
-
-
- /* These hook functions simply pass control to the appropriate function
- * in the Plugin object.
- */
- static arHookResult OurClickHook( ModuleParamBlock* pb, arClickLocation loc,
- Point where, pShort modifiers, pShort clickCount,
- arNoteID note, arFieldID field,
- arPathID path ENDP )
- {
- return ((Plugin*)(pb->moduleRefcon))->ClickNotify( loc, where, modifiers,
- clickCount, note, field,
- path );
- }
-
-
- static arHookResult OurKeyHook( ModuleParamBlock* pb, pShort theChar, pShort key,
- pShort modifiers ENDP )
- {
- return ((Plugin*)(pb->moduleRefcon))->KeyNotify(theChar, key, modifiers);
- }
-
-
- static arHookResult OurMenuHook( ModuleParamBlock* pb, Integer commandCode,
- Integer commandParam, pShort modifiers ENDP )
- {
- return ((Plugin*)(pb->moduleRefcon))->MenuNotify( commandCode, commandParam,
- modifiers );
- }
-
-
- static arHookResult OurFieldHook( ModuleParamBlock* pb, arNoteID note,
- arFieldID field, arFieldAction action,
- const char* choiceText ENDP )
- {
- return ((Plugin*)(pb->moduleRefcon))->FieldNotify( note, field, action,
- choiceText );
- }
-
-
- static void OurTopicHook( ModuleParamBlock* pb, arTopicID newTopic,
- arWindowID newWindow, arTopicAction action ENDP )
- {
- ((Plugin*)(pb->moduleRefcon))->TopicNotify(newTopic, newWindow, action);
- }
-
-
- static void OurTickHook(ModuleParamBlock* pb ENDP)
- {
- ((Plugin*)(pb->moduleRefcon))->TickNotify();
- }
-
-
- static arHookResult OurFileHook(ModuleParamBlock* pb, arFileAction action ENDP)
- {
- return ((Plugin*)(pb->moduleRefcon))->FileNotify(action);
- }
-
-
- static arHookResult OurQuitHook(ModuleParamBlock* pb ENDP)
- {
- return ((Plugin*)(pb->moduleRefcon))->QuitNotify();
- }
-
-
- static void OurATMHook(ModuleParamBlock* pb ENDP)
- {
- ((Plugin*)(pb->moduleRefcon))->ATMNotify();
- }
-
-
- /* These are the actual hooks that we post. They just call back to the
- * class object.
- */
- static Integer OurImportHook( ModuleParamBlock* pb, const FSSpec* /*file*/,
- pShort fRefnum, Integer formatID,
- Boolean sniff, arTopicID destTopic ENDP )
- {
- return ((Plugin*)(pb->moduleRefcon))->ImportNotify( fRefnum, formatID,
- sniff, destTopic );
- } // OurImportHook
-
-
- static Integer OurExportHook( ModuleParamBlock* pb, const FSSpec* /*file*/,
- pShort fRefnum, Integer /*formatID*/,
- arEFType /*type*/, arTopicID srcTopic ENDP )
- {
- return ((Plugin*)(pb->moduleRefcon))->ExportNotify(fRefnum, srcTopic);
- } // OurExportHook
-
-
- /*************************************************************************/
- /****************************** SimpleStream *****************************/
- /*************************************************************************/
-
- /* This is a simple read stream class for reading from a file. All it
- * really does is provide a 4K read buffer so that multiple small reads are
- * more efficient. You must pass it the refnum of a file that is already open.
- * It will not close the file.
- */
- class SimpleStream
- {
- public:
- SimpleStream(Short fRefnum, char *buffer);
-
- char ReadByte();
- void ReadBlock(char *buffer, Integer length);
-
- Integer BytesRemaining();
- void Reset();
-
- protected:
- short fRefnum; //The HFS Ref num of the file we are reading from.
- char *readBuffer; //This class reads bytes from the file 4K at a time.
- //This is the physical buffer.
- char *bufEnd; //Pointer to the first byte after the end of the buffer
- char *nextByte; //Pointer to the next byte in the buffer to be read.
-
- Integer bufferReadOffset; //Index in the file of the next byte to be buffered.
- Integer fileSize; //Size of the file itself.
-
- void AdvanceBuffer();
- }; // SimpleStream
-
-
- /* Create the SimpleStream object.Read the length of the file. Read the first 4K.
- * This assumes that the file is already open for read. This class will
- * not open or close the file. It does not own the buffer passed in.
- */
- SimpleStream::SimpleStream(Short newFRefnum,char *newBuffer)
- {
- fRefnum = newFRefnum;
- readBuffer = newBuffer;
- bufEnd = readBuffer + 4096;
- nextByte = &readBuffer[0];
- bufferReadOffset = 0;
-
- long temp;
- GetEOF(fRefnum,&temp);
- fileSize = (Integer)temp;
-
- //Reset the file to 0.
- SetFPos(fRefnum,fsFromStart,0);
- AdvanceBuffer();
- } // SimpleStream constructor
-
-
- /* Reset the file and the cache to the beginning. */
- void SimpleStream::Reset()
- {
- //Reset the file
- SetFPos(fRefnum,fsFromStart,0);
-
- //Reset the cache
- nextByte = &readBuffer[0];
- bufferReadOffset = 0;
- AdvanceBuffer();
- } // Reset
-
-
- /* Advance the buffer by 4K or to the end of the file, if there are no
- * error conditions. Reset the next byte pointer.
- */
- void SimpleStream::AdvanceBuffer()
- {
- if (fileSize < 1)
- return;
-
- long bytesToRead = Min(4096, fileSize - bufferReadOffset);
-
- if (bytesToRead == 0)
- {
- fileSize = -1;
- return;
- }
-
- if (FSRead(fRefnum,&bytesToRead,readBuffer) != noErr)
- fileSize = -1;
-
-
- nextByte = readBuffer;
- bufferReadOffset += (Integer)bytesToRead;
- bufEnd = readBuffer + bytesToRead;
- } // AdvanceBuffer
-
-
- /* Return the number of bytes remaining in the file, after the current mark. */
- Integer SimpleStream::BytesRemaining()
- {
- if (fileSize < 1)
- return 0;
-
- //return the amount left to read, plus what's left in the buffer.
- return (fileSize - bufferReadOffset) + (bufEnd - nextByte);
- } // BytesRemaining
-
-
- /* Read the next byte from the file. Advance the pointer if needed. This may
- * leave the buffer empty and not advance.
- */
- char SimpleStream::ReadByte()
- {
- if (nextByte >= bufEnd)
- AdvanceBuffer();
-
- return *(nextByte++);
- } // ReadByte
-
-
- /* Read a block of the given length from the file. The buffer must be at least as
- * long as the passed length. The length can be any size.
- */
- void SimpleStream::ReadBlock(char *buffer, Integer length)
- {
- Integer bytesToRead = length;
-
- while (bytesToRead > 0)
- {
- Integer bytesInCurBuffer = bufEnd - nextByte;
- Integer bytesOnThisRead = Min(bytesToRead,bytesInCurBuffer);
- if (bytesOnThisRead > 0)
- {
- BlockMove(nextByte,buffer,bytesOnThisRead);
- nextByte += bytesOnThisRead;
- buffer += bytesOnThisRead;
- bytesToRead -= bytesOnThisRead;
- }
-
- if (nextByte >= bufEnd)
- AdvanceBuffer();
-
- } // while (bytesToRead > 0)
-
- } // ReadBlock
-
-
- /*************************************************************************/
- /********************************* Plugin ********************************/
- /*************************************************************************/
-
- /* Construct a Plugin object. This is called once, from the OurModuleRoot
- * function, when the module is initialized (i.e. at application startup time).
- */
- Plugin::Plugin(const ArrangeCallbackTbl* theCalls)
- {
- // Record the callback table for future use.
- calls = theCalls;
-
- /* These commands, if un-commented-out, register this plugin to be called
- * by Arrange when various events occur. For example, if you un-comment-out
- * the call to SetClickHook, then Plugin::ClickNotify will be called
- * whenever the user clicks in any of the locations described in the
- * arClickLocation enum.
- */
- // calls->ui->SetClickHook(OurClickHook, 0, true);
- // calls->ui->SetKeyHook (OurKeyHook, 0, true, charFilter, keyFilter, modFilter);
- // calls->ui->SetMenuHook (OurMenuHook, 0, true, whichCommand);
- // calls->ui->SetFieldHook(OurFieldHook, 0, true, whichField);
- // calls->ui->SetTopicHook(OurTopicHook, 0, true);
- // calls->ui->SetTickHook (OurTickHook, 0, true);
- // calls->ui->SetFileHook (OurFileHook, 0, true);
- // calls->ui->SetQuitHook (OurQuitHook, 0, true);
- // calls->ui->SetATMHook (OurATMHook, 0, true);
-
- // Add an item to the About Plugins menu for this plugin.
- calls->ui->AddMenuItem(mPluginAbout, "About Meeting Maker", 0, aboutCmdCode, 0);
-
- // Register ourselves to be called when our About command is chosen.
- calls->ui->SetMenuHook(OurMenuHook, 0, true, aboutCmdCode);
-
- // Register hooks for our import and export formats.
- OSType textType = 'TEXT';
-
- char buffer[256];
- getindstring(&buffer[0],rMMImportExportNames,rImportName);
- calls->ui->RegisterImportFormat( buffer, 'MMkr', &textType, 1,
- OurImportHook, 0 );
-
- getindstring(&buffer[0],rMMImportExportNames,rExportName);
- calls->ui->RegisterExportFormat( buffer, 'MMkr', efExport,
- OurExportHook, 0, 'TEXT', 'MMss' );
- } // Plugin constructor
-
-
- /* Dispose of a Plugin object. This is called when Arrange terminates. You
- * should free up any data structures which you allocation in the Plugin
- * constructor (above).
- */
- Plugin::~Plugin()
- {
- } // ~Plugin
-
-
- /* This function is called whenever the user clicks in an "interesting place"
- * in a document window. It should return true if we handle the click, false
- * (the normal case) when the click should be passed along to other plug-ins
- * or to Arrange's normal event processing. See the documentation for
- * SetClickHook for more details.
- */
- arHookResult Plugin::ClickNotify( arClickLocation /*loc*/, Point /*where*/,
- Short /*modifiers*/, Short /*clickCount*/,
- arNoteID /*note*/, arFieldID /*field*/,
- arPathID /*path*/ )
- {
- return false; // Let Arrange handle the event
- } // ClickNotify
-
-
- /* This function is called whenever the user types a key which matches the
- * filters we pass to SetKeyHook in Plugin's constructor. It should return
- * true if we handle the event, false (the normal case) when the event
- * should be passed along to other plug-ins or to Arrange's normal event
- * processing. See the documentation for SetKeyHook for more details.
- */
- arHookResult Plugin::KeyNotify( Short /*theChar*/, Short /*key*/,
- Short /*modifiers*/ )
- {
- return false; // Let Arrange handle the event
- } // KeyNotify
-
-
- /* This function is called whenever the user chooses a menu item for which
- * we have registered (via the SetMenuHook function). It should return true
- * if we handle the command, false (the normal case) when the command should
- * be passed along to other plug-ins or to Arrange's normal event processing.
- * See the documentation for SetMenuHook for more details.
- */
- arHookResult Plugin::MenuNotify( Integer commandCode, Integer /*commandParam*/,
- Short /*modifiers*/ )
- {
- /* If this is our About menu item, display our about-box dialog and return
- * true to indicate we handled the command.
- */
- if (commandCode == aboutCmdCode)
- {
- Alert(ModuleRsrcID, nil);
- return true;
- }
- else
- return false; // Let Arrange handle the event
-
- } // MenuNotify
-
-
- /* If we register to recieve events for a field by calling SetFieldHook, this
- * function will be called for any events in that field. It should return
- * false in almost all cases. See the documentation for SetFieldHook for
- * more details.
- */
- arHookResult Plugin::FieldNotify( arNoteID /*note*/, arFieldID /*field*/,
- arFieldAction /*action*/,
- const char* /*choiceText*/ )
- {
- return false;
- } // FieldNotify
-
-
- /* This function is called whenever the user switches windows or changes
- * the current folder/topic/view in the front window. See the documentation
- * for SetTopicHook for more details.
- */
- void Plugin::TopicNotify( arTopicID /*newTopic*/, arWindowID /*newWindow*/,
- arTopicAction /*action*/ )
- {
- } // TopicNotify
-
-
- /* This function is called periodically, whenever Arrange recieves a null
- * event from the Event Manager.
- */
- void Plugin::TickNotify()
- {
- } // TickNotify
-
-
- /* This function is called whenever various file-level events occur. It
- * should return true in almost all cases. See the documentation for
- * SetFileHook for more details.
- */
- arHookResult Plugin::FileNotify(arFileAction /*action*/)
- {
- return true;
- } // FileNotify
-
-
- /* This function is called if the user voluntarily quits Arrange. It should
- * return true to allow the user to quit, false to prevent it. See the
- * documentation for SetQuitHook for more details.
- */
- arHookResult Plugin::QuitNotify()
- {
- return true;
- } // QuitNotify
-
-
- /* This function is called whenever the user clicks in the menu bar or types
- * a command key, just before processing the event. It should do any fixing
- * up of menus which might be necessary based on the current state of affairs.
- * See the documentation for SetATMHook for more details.
- */
- void Plugin::ATMNotify()
- {
- } // ATMNotify
-
-
- /*************************************************************************/
- /********************************* Import ********************************/
- /*************************************************************************/
-
- /* Read to the next tab or CR. Load the data, minus
- * delimiter, into the buffer, and return the length read (minus the delimiter).
- * Signal true if the delimiter was the tab char. EOF counts as CR. The buffer must
- * be at least 4K long. Note that this will read the delimiter character, although
- * it will not return it in the buffer. So, repeated calls to this routine will
- * return all the data inbetween the delimiters in the stream, but none of the
- * delimiters:
- *
- * while (ReadToNextDelimiter(stream,buffer,wasTab) != 0)
- * ... no delimiters in the buffer ...
- *
- * Note that this routine will always read until either the end of the file or the
- * next delimiter, but it will store no more than bufferLength in the buffer.
- */
- Integer ReadToNextDelimiter( SimpleStream *stream, OUT char *buffer,
- Integer bufferLength, OUT Boolean &wasTab )
- {
- char *startBuffer = buffer;
- while (stream->BytesRemaining() > 0)
- {
- char nextChar = stream->ReadByte();
- if (nextChar == '\t')
- {
- wasTab = true;
- return buffer - startBuffer;
- }
- else if (nextChar == '\n')
- {
- wasTab = false;
- return buffer - startBuffer;
- }
-
- if (buffer - startBuffer < bufferLength)
- {
- buffer[0] = nextChar;
- buffer++;
- }
- }
-
- wasTab = false;
- return buffer - startBuffer;
- } // ReadToNextDelimiter
-
-
- /* Lookup the name of the Arrange field that corresponds to the field
- * in the MM file.
- */
- static void GetArrangeNameForMMField(MeetingMakerField theField, OUT char *name)
- {
- name[0] = 0;
- getindstring(&name[0],rFieldNameStrings,theField + 1);
- } // GetArrangeNameForMMField
-
-
- /* Return the arrange field for the given meeting maker field, or nil if it
- * doesn't exist. You should call AllocateFieldsAndTypes below before
- * calling this to make the field exist. This actually looks the arFieldID up
- * in the current document.
- */
- arFieldID Plugin::GetArrangeFieldForMMField(MeetingMakerField field)
- {
- char buffer[256];
-
- GetArrangeNameForMMField(field,buffer);
- if (buffer[0] != '<')
- {
- //Lookup the field
- return calls->sysObj->LookupObjectName(arField,buffer,false);
- }
-
- return 0;
- } // GetArrangeFieldForMMField
-
-
- // Create the note type and fields for the MM import, if they don't already exist.
- arTypeID Plugin::AllocateFieldsAndTypes()
- {
- char buffer[256];
-
- //See if the type exists
- getindstring(&buffer[0],rFieldNameStrings,rNoteTypeName);
- arTypeID type = calls->sysObj->LookupObjectName(arNoteType,buffer,false);
-
- if (type == 0)
- {
- type = calls->sysObj->CreateNoteType(buffer,true);
-
- //Remove the note text field
- calls->notes->RemoveField(type, calls->sysObj->GetBuiltInObject(boNoteTextField));
- }
-
- for (Integer index = mmTitle; index < mmNotes + 1; index++)
- {
- //Create each of these if needed, and add it to the type if not present.
- GetArrangeNameForMMField((MeetingMakerField)index,buffer);
- if (buffer[0] != '<')
- {
- //Lookup the field
- arFieldID field = calls->sysObj->LookupObjectName(arField,buffer,false);
- if (field == 0)
- {
- arFieldType theFieldType = arFTText;
- if (index == mmDate || index == mmStartTime || index == mmDuration)
- theFieldType = arFTDateTime;
-
- field = calls->sysObj->CreateField(buffer,theFieldType,true);
- }
-
- if (!calls->notes->NoteHasField(type,field,true))
- calls->notes->AddField(type,field,0);
- }
- }
-
- return type;
- } // AllocateFieldsAndTypes
-
-
- /* This assumes that the file is already open for read. This will not close the
- * file. Import the file using the Meeting Maker format.
- */
- Integer Plugin::ImportNotify( Short fRefnum, Integer formatID,
- Boolean sniff, arTopicID destTopic )
- {
- //Is this the right format?
- if (formatID != 'MMkr')
- return -1;
-
- arTopicInfo info;
- calls->sysObj->GetTopicInfo(destTopic,&info);
-
- if (info.type == arFolder)
- return -1; //Can't import into a folder
- else if (info.type == arView)
- destTopic = info.parent; //When in a view, import into the parent topic.
-
- //Allocate our buffers and the read stream.
- char *readBuffer = (char*)calls->mem->AllocMem(4096,amPtr);
- if (readBuffer == nil)
- return -1;
-
- SimpleStream stream(fRefnum,readBuffer);
- char *buffer = (char*)calls->mem->AllocMem(4096,amPtr);
- if (buffer == nil)
- {
- calls->mem->DeallocMem(readBuffer,amPtr);
- return -1;
- }
-
- Boolean wasTab;
- char *tempBuffer = buffer;
-
- //Read past the first row, this is just the labels
- while (stream.BytesRemaining() > 0)
- {
- tempBuffer += ReadToNextDelimiter(&stream,tempBuffer,4096,wasTab);
- tempBuffer[0] = 0;
- if (!wasTab)
- break;
- }
-
- //If we are sniffing the file, check to see if the first line is what
- //we expect it to be.
- if (sniff)
- {
- //The first line should look like this if this is the right format.
- char *firstLine = "TitleLocationDateStart TimeDurationPrivateFlexibleLabelAgenda/Notes";
-
- Boolean isGood = strcmp(firstLine,buffer) == 0;
- calls->mem->DeallocMem(readBuffer,amPtr);
- calls->mem->DeallocMem(buffer,amPtr);
-
- if (isGood)
- return 10000;
- else
- return -1;
- }
-
- //Make sure the Arrange fields and types are allocated before the actual import.
- arTypeID type = AllocateFieldsAndTypes();
- arFieldID topicListField = calls->sysObj->GetBuiltInObject(boTopicContentsField);
-
- //Generate notes until we run out of file.
- while (stream.BytesRemaining() > 0)
- {
- //Create the note
- arNoteID note = GenerateMMNote(&stream,type,buffer);
-
- if (note != 0)
- {
- //See if there is already a meeting with the same title, location and
- //time. If so, destroy the new note.
- if (MMNoteExists(note))
- calls->notes->DestroyNote(note);
- else
- calls->data->AddListFieldEntry(destTopic,topicListField,0,note,nullSFF);
- }
- }
-
- //Deallocate the buffers.
- calls->mem->DeallocMem(readBuffer,amPtr);
- calls->mem->DeallocMem(buffer,amPtr);
- return 0;
- } // ImportNotify
-
-
- /* Utility used during import. Reading from the current position of the stream,
- * create a current note (assume that the file is currently at the beginning of
- * a line. Return 0 if there is an error, otherwise return the note.
- * This assumes that AllocateFieldsAndTypes has been called.
- */
- arNoteID Plugin::GenerateMMNote(SimpleStream *stream,arTypeID type, char *buffer)
- {
- //Make the note.
- arNoteID resultNote = calls->notes->CreateNote(type,true);
- Boolean wasTab;
-
- //Loop through the fields on the line, importing data. There are some
- //special cases: the 'Start Date' and 'Time' field get packed together into
- //one date/time field, and the duration field gets converted into an end time
- //based on the start date/time.
- for (Integer index = mmTitle; index <= mmNotes; index++)
- {
- //Read the next column.
- Integer fieldSize = ReadToNextDelimiter(stream,buffer,4096,wasTab);
- buffer[fieldSize] = 0;
-
- //Find the field to put the data in.
- arFieldID field = GetArrangeFieldForMMField((MeetingMakerField)index);
-
- //If the field for this note is not present, then we just skip the col.
- //This happens when the name <skip> is present in the field name resource list.
- if (field == 0)
- continue;
-
- //Make sure the note has the target field
- if (!calls->notes->NoteHasField(resultNote,field,true))
- calls->notes->AddField(resultNote,field,0);
-
- //Add the data to the field.
- //Special cases for the date, start, and duration fields.
- if ((MeetingMakerField)index == mmDate && wasTab)
- {
- //The 'Date' field is actually stored in two columns in the list, the
- //'date' col and the 'start' col. We concatenate, with an '@' in the middle,
- //so Arrange can parse the date, and then store, advancing past the
- //next col.
- buffer[fieldSize++] = '@';
- Integer timeSize = ReadToNextDelimiter(stream,&buffer[fieldSize],4096,wasTab);
- buffer[fieldSize + timeSize] = 0;
-
- //Make sure to advance the index to the next column, since we have read two
- //columns at once.
- index++;
-
- //Store the date as text, and tell Arrange to parse it.
- calls->data->SetFieldText(resultNote,field,buffer,
- fieldSize + timeSize,sfDoParse);
- }
- else if ((MeetingMakerField)index == mmDuration)
- {
- //The duration field gets turned into an end date relative to the
- //already stored start date. We dig it out, parse into seconds, and then
- //store the offsetted value.
-
- arFieldID startDateField = GetArrangeFieldForMMField(mmDate);
- arDate startDate = calls->data->GetFieldDate(resultNote,startDateField);
-
- //Find the ':' in the middle
- char *colonLoc = buffer;
- while (colonLoc[0] != ':' && colonLoc[0] != 0)
- colonLoc++;
-
- //Change it to a 0, and parse the two numbers on either side.
- colonLoc[0] = 0;
- long hours,minutes;
-
- stringtonum(buffer,&hours);
- stringtonum(colonLoc+1,&minutes);
- Integer duration = (Integer)(hours*60*60 + minutes*60); //In seconds
-
- //Set the field, and let Arrange generate the text.
- calls->data->SetFieldDate(resultNote,field,startDate + duration,sfDoFormat);
- }
- else
- //All other fields are just stored as text.
- calls->data->SetFieldText(resultNote,field,buffer,fieldSize,nullSFF);
-
- //If the current field was the end of the line, stop reading and return what
- //we have.
- if (!wasTab)
- return resultNote;
- }
-
- //Return the note
- return resultNote;
- } // GenerateMMNote
-
-
- /* Look through the document for another note that has the same Title, Location and
- * Start Time. Return true if you find one, false if not.
- */
- Boolean Plugin::MMNoteExists(arNoteID newNote)
- {
- if (newNote == 0)
- return false;
-
- arFilterClause clauseList[3];
-
- clauseList[0].type = fEquals;
- clauseList[0].field = GetArrangeFieldForMMField(mmTitle);
- calls->data->GetFieldText(newNote,clauseList[0].field,128,clauseList[0].text);
-
- clauseList[1].type = fEquals;
- clauseList[1].field = GetArrangeFieldForMMField(mmLocation);
- calls->data->GetFieldText(newNote,clauseList[1].field,128,clauseList[1].text);
-
- clauseList[2].type = fEquals;
- clauseList[2].field = GetArrangeFieldForMMField(mmDate);
- clauseList[2].date = calls->data->GetFieldDate(newNote,clauseList[2].field);
-
- arTypeID type = calls->notes->GetNoteType(newNote);
- OWN arListID matches = calls->search->FilterNotes(type,(arFilterFlags)0,3,clauseList);
-
- //There will be at least 1 entry in the list, the newNote. If there is more
- //than 1, then there is a match, and we return true.
- Boolean result = calls->list->GetListLen(matches) > 1;
- calls->list->DisposeList(matches);
- return result;
- } // MMNoteExists
-
-
- /*************************************************************************/
- /********************************* Export ********************************/
- /*************************************************************************/
-
- /* Write the col list header out into the given file. Assume the file is
- * set to pos 0.
- */
- static void WriteMMHeader(Short fRefNum)
- {
- char *buffer = "Title\tLocation\tDate\tStart Time\tDuration\tPrivate\tFlexible\tLabel\tAgenda/Notes\n";
- long bufCount = strlen(buffer);
- FSWrite(fRefNum,&bufCount,buffer);
- } // WriteMMHeader
-
-
- /* Export the current topic to the given file. Assumes that the file is already
- * open, and will leave the file open and unflushed. Note that this doesn't care if
- * the notes in the topic are meeting notes. It will export the data as best it can.
- */
- Integer Plugin::ExportNotify(Short fRefnum, arTopicID srcTopic )
- {
- if (SetFPos(fRefnum,fsFromStart,0) != noErr)
- return 1;
-
- WriteMMHeader(fRefnum);
-
- //Iterate through the notes
- arFieldID topicContentsField = calls->sysObj->GetBuiltInObject(boTopicContentsField);
- Integer topicCount = calls->data->GetFieldListLen(srcTopic,topicContentsField);
- for (Integer index = 0; index < topicCount; index++)
- {
- //For each note, call the write routine
- arNoteID curNote = calls->data->GetFieldListEntry(srcTopic,topicContentsField,index);
- WriteMMNote(curNote,fRefnum);
- }
-
- return 0;
- } // ExportNotify
-
-
- /* Generate a string based on the given date. Generate either the date
- * portion of the string or the time portion.
- */
- static void GetDateAsString(arDate date, Boolean doTime, OUT char *buffer)
- {
- buffer[0] = 0;
- Handle theIUHandle;
- LongDateTime secs;
-
- ((LongDateCvt*)&secs)->hl.lLow = date;
- ((LongDateCvt*)&secs)->hl.lHigh = 0;
- if (!doTime)
- {
- theIUHandle = Handle(IUGetIntl(0));
- iuldatestring( &secs,
- shortDate,
- buffer,
- theIUHandle);
- }
- else
- {
- theIUHandle = Handle(IUGetIntl(0));
- iultimestring(&secs,false,buffer,theIUHandle);
- }
-
- } // GetDateAsString
-
-
- /* Write the given note out as a MM tabline. If any of the fields are 'skip'
- * fields, or are not present in the note, just output an empty column. The
- * startDate field will be split into two columns (date, time), and the end
- * date gets converted to a duration.
- */
- void Plugin::WriteMMNote(arNoteID note,Short fRefNum)
- {
- //go through the fields. If the cur field is not a skip field, and the note
- //has the field, write out the text. If the cur field is the date field,
- //replace the ampersand with a tab, and output, skipping the next col. If the
- //cur field is the duration field, calc the duration, and output HH:MM. Output
- //tab for blank or missing fields.
- char buffer[4096];
- long bufCount;
- for (Integer index = mmTitle; index <= mmNotes; index++)
- {
- arFieldID curField = GetArrangeFieldForMMField((MeetingMakerField)index);
- if (curField != 0 && calls->notes->NoteHasField(note,curField,false) )
- {
- if ((MeetingMakerField)index == mmDate)
- {
- #ifdef OLDCODE
-
- bufCount = Min(4096,calls->data->GetFieldTextLen(note,curField));
- calls->data->GetFieldText(note,curField,4096,buffer);
-
- //Find the '@', replace it with tab (so this field fills two cols, date and start),
- //and advance the index 1 (to skip the 'start' part which we just wrote out.
- char *tempBuffer = buffer;
- while (tempBuffer[0] != 0)
- {
- if (tempBuffer[0] == '@')
- {
- tempBuffer[0] = '\t';
- index++;
- break;
- }
-
- tempBuffer++;
- }
-
- //If we didn't find the '@', tack a tab on the end anyway, to advance.
- if (tempBuffer[0] == 0)
- {
- tempBuffer[0] = '\t';
- index++;
- bufCount++;
- }
- #endif
-
- arDate date = calls->data->GetFieldDate(note,curField);
- if (date == missingDateToken)
- {
- //Missing date, just output blanks
- buffer[0] = ' ';
- buffer[1] = '\t';
- buffer[2] = ' ';
- index++;
- bufCount = 3;
- }
- else
- {
- buffer[0] = 0;
- GetDateAsString(date, false, buffer);
- Integer dateLen = strlen(buffer);
- GetDateAsString(date,true,&buffer[dateLen]);
- buffer[dateLen] = '\t';
- index++;
- bufCount = strlen(buffer);
- }
- }
- else if ((MeetingMakerField)index == mmDuration)
- {
- //Calculate the duration from the start and end fields.
- //Default the duration to 1 hour if either field is missing.
- arFieldID startField = GetArrangeFieldForMMField(mmDate);
-
- long durationSeconds;
- if (startField == 0)
- durationSeconds = 3600;
- else
- {
- arDate startDate = calls->data->GetFieldDate(note,startField);
- arDate endDate = calls->data->GetFieldDate(note,curField);
-
- if (startDate == missingDateToken || endDate == missingDateToken)
- durationSeconds = 3600;
- else
- durationSeconds = endDate - startDate;
- }
-
- long durationHours = durationSeconds/3600;
- long durationMinutes = (durationSeconds - (durationHours*3600))/60;
-
- buffer[0] = 0;
- if (durationHours == 0)
- {
- buffer[0] = '0';
- buffer[1] = '0';
- buffer[2] = 0;
- }
- else
- numtostring(durationHours,buffer);
-
- Integer hoursLen = strlen(buffer);
- buffer[hoursLen] = ':';
- buffer[hoursLen + 1] = 0;
- if (durationHours == 0)
- {
- buffer[hoursLen + 1] = '0';
- buffer[hoursLen + 2] = '0';
- buffer[hoursLen + 3] = 0;
- }
- else
- numtostring(durationMinutes,&buffer[hoursLen + 1]);
-
- bufCount = strlen(buffer);
- }
- else
- {
- bufCount = Min(4096,calls->data->GetFieldTextLen(note,curField));
- calls->data->GetFieldText(note,curField,4096,buffer);
- }
-
- if (bufCount > 0)
- FSWrite(fRefNum,&bufCount,buffer);
- }
-
- //Write out the tab at the end of the column.
- bufCount = 1;
- buffer[0] = '\t';
- buffer[1] = 0;
- FSWrite(fRefNum,&bufCount,buffer);
- }
-
- //End the line
- bufCount = 1;
- buffer[0] = '\n';
- buffer[1] = 0;
- FSWrite(fRefNum,&bufCount,buffer);
- } // WriteMMNote
-